#include "stdafx.h"
#include "AudioHostForCA.h"
#include <strsafe.h>
//#include <avrt.h>
//#pragma comment(lib,"avrt.lib")

#ifdef DEBUG
#include <fstream>
static std::ofstream f("t.wav",std::ios::binary);
#endif

typedef HANDLE (WINAPI *LPAvSA) (LPCSTR TaskName,LPDWORD TaskIndex);
typedef BOOL (WINAPI *LPAvR) (HANDLE AvrtHandle);

LPAvSA AvSetMmThreadCharacteristicsA=NULL;
LPAvR AvRevertMmThreadCharacteristics=NULL;

HMODULE hDll=NULL;

BOOL CAudioHostForCA::m_fModelsInited=FALSE;
void CAudioHostForCA::InitModels()
{
	if(!m_fModelsInited)
	{
		if(hDll==NULL)
		{
			hDll=LoadLibraryA("avrt.dll");
			if(hDll!=NULL)
			{
				AvSetMmThreadCharacteristicsA=(LPAvSA)GetProcAddress(hDll,"AvSetMmThreadCharacteristicsA");
				AvRevertMmThreadCharacteristics=(LPAvR)GetProcAddress(hDll,"AvRevertMmThreadCharacteristics");
				m_fModelsInited=TRUE;
			}
		}
		
	}
}

int GetDeviceList(IMMDeviceCollection *deviceCollection,char* pszDevNames,LPWSTR DefauldDevId)
{
    HRESULT hr;

	UINT deviceCount;
    hr = deviceCollection->GetCount(&deviceCount);
	int rt=-1;

	for (UINT i = 0 ; i < deviceCount ; i += 1)
	{
		LPWSTR deviceName=NULL;
		IMMDevice *device;
		LPWSTR deviceId;

		hr = deviceCollection->Item(i, &device);
		hr = device->GetId(&deviceId);
		if(lstrcmpW(DefauldDevId,deviceId)==0)
		{
			rt=i;
		}

		IPropertyStore *propertyStore;
		hr = device->OpenPropertyStore(STGM_READ, &propertyStore);
		SafeRelease(&device);

		PROPVARIANT friendlyName;
		PropVariantInit(&friendlyName);
		hr = propertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
		SafeRelease(&propertyStore);

		WCHAR tws[128]={0};
		hr = StringCbPrintfW(tws, 128, L"%s", STRSAFE_LPCSTR((friendlyName.vt != VT_LPWSTR&&friendlyName.vt != VT_LPSTR) ? L"Unknown" : friendlyName.pwszVal));
		WideCharToMultiByte (CP_OEMCP,NULL,tws,-1,&pszDevNames[i*128], 128,NULL,FALSE);
	    
		PropVariantClear(&friendlyName);
		CoTaskMemFree(deviceId);
	}
	return rt;
}

void CAudioHostForCA::GetDevices()
{
	HRESULT hr;
    IMMDeviceEnumerator *deviceEnumerator = NULL;
    IMMDeviceCollection *deviceCollection = NULL;
	IMMDevice *device = NULL;
	EDataFlow enumDevTypes[2]={eRender,eCapture};
	int* DevCounts[2]={&m_nLDevs,&m_nCDevs};
	int* DefDevIds[2]={&m_iDefLDev,&m_iDefCDev};
	char* DevNames[2]={(char*)m_pszLDevNames,(char*)m_pszCDevNames};

	for(int e=0;e<(sizeof(enumDevTypes)/sizeof(enumDevTypes[0]));++e)
	{
		if(e==0&&m_eDrvType==DIRECT_SOUND)
		{
		}
		else
		{
			hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
			if (SUCCEEDED(hr))
			{
				hr = deviceEnumerator->EnumAudioEndpoints(enumDevTypes[e], DEVICE_STATE_ACTIVE, &deviceCollection);
			}
			
			UINT deviceCount;
			if (SUCCEEDED(hr))
			{
				hr = deviceCollection->GetCount(&deviceCount);
			}

			/**	Get all device names and the default device name
			 */
			IMMDevice *device=NULL;

			if (SUCCEEDED(hr))
			{
				/** Set num of Device
				 */
				*(DevCounts[e])=deviceCount;
				hr = deviceEnumerator->GetDefaultAudioEndpoint(enumDevTypes[e], eMultimedia, &device);
			}
			if (SUCCEEDED(hr))
			{
				LPWSTR deviceId=NULL;
				hr = device->GetId(&deviceId);
				if (SUCCEEDED(hr))
				{
					memset(DevNames[e],0,16*128);
					*(DefDevIds[e])=GetDeviceList(deviceCollection,DevNames[e],deviceId);
				}
			}
			SafeRelease(&deviceCollection);
			SafeRelease(&deviceEnumerator);
		}
	}
}

bool CAudioHostForCA::InitializeStreamSwitch()
{
    HRESULT hr = _CAudioClient->GetService(IID_PPV_ARGS(&_CAudioSessionControl));
	hr = _CAudioSessionControl->RegisterAudioSessionNotification(this);
	_StreamSwitchCompleteEvent = CreateEvent(NULL,TRUE,TRUE,NULL);
	switch(m_eDrvType)
	{
		case DIRECT_SOUND:
			break;
		case CORE_AUDIO:
			hr = _LAudioClient->GetService(IID_PPV_ARGS(&_LAudioSessionControl));
			hr = _LAudioSessionControl->RegisterAudioSessionNotification(this);
			break;
	}
    
    hr = _DeviceEnumerator->RegisterEndpointNotificationCallback(this);
    return true;
}

bool CAudioHostForCA::InitializeAudioEngine()
{
	HRESULT hr=S_OK;
	IMMDevice * IEP[2]={_CEndpoint,_LEndpoint};
	hr=_CEndpoint->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&_Volume));
	hr=_Volume->SetMasterVolumeLevelScalar(((float)m_nVolume)/100.0f,NULL);
	IAudioClient * IAC[2]={_CAudioClient,_LAudioClient};
	for(int i=0;i<2;++i)
	{
		if(i==1&&m_eDrvType==DIRECT_SOUND)
		{
		}
		else
		{
			//Gey minimum buffer size
			WAVEFORMATEXTENSIBLE wfx={0};
			memcpy(&wfx,&m_wfx,sizeof(WAVEFORMATEXTENSIBLE));

			REFERENCE_TIME minDur,defDur;
			IAC[i]->GetDevicePeriod(&defDur,&minDur);

			int minsz=(int)(((double)(wfx.Format.nAvgBytesPerSec)*(double)(minDur))/10000000.0f);
			MIN_BUFFER_SIZE=(MIN_BUFFER_SIZE>minsz)?MIN_BUFFER_SIZE:minsz;
			if(m_nBufferSize<MIN_BUFFER_SIZE)
			{
				m_nBufferSize=MIN_BUFFER_SIZE;
			}
			//////////////////
			REFERENCE_TIME bufferDuration = (REFERENCE_TIME)(10000.0 *                         // (REFERENCE_TIME / ms) *
												  1000 *                            // (ms / s) *
												  (m_nBufferSize/wfx.Format.nBlockAlign)/                      // frames /
												  wfx.Format.nSamplesPerSec +      // (frames / s)
												  0.5);                             // rounding

			if(m_iInChannels==1&&i==0)
			{
				wfx.Format.nChannels=1;
				wfx.Format.nBlockAlign=wfx.Format.wBitsPerSample*wfx.Format.nChannels/8;
				wfx.Format.nAvgBytesPerSec=wfx.Format.nSamplesPerSec*wfx.Format.nBlockAlign;
				wfx.dwChannelMask=SPEAKER_FRONT_CENTER;
			}	
			

			hr = IAC[i]->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
															bufferDuration,//((i==0)?(100000):(bufferDuration*10000)), 
															bufferDuration,//((i==0)?(100000):0),
															(WAVEFORMATEX *)&wfx,
															NULL);
			
			//
			//  When rendering in exclusive mode event driven, the HDAudio specification requires that the buffers handed to the device must 
			//  be aligned on a 128 byte boundary.  When the buffer is initialized and the resulting buffer size would not be 128 byte aligned,
			//  we need to "swizzle" the periodicity of the engine to ensure that the buffers are properly aligned.
			//
			if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) 
			{
				UINT32 bufferSize;
				hr = IAC[i]->GetBufferSize(&bufferSize);
				SafeRelease(&IAC[i]);
				bufferDuration = (REFERENCE_TIME)(10000.0 *                         // (REFERENCE_TIME / ms) *
												  1000 *                            // (ms / s) *
												  bufferSize /                      // frames /
												  wfx.Format.nSamplesPerSec +      // (frames / s)
												  0.5);                             // rounding
				hr = IEP[i]->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&IAC[i]));
				hr = IAC[i]->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, 
											  AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, 
											  bufferDuration, 
											  bufferDuration, 
											  (WAVEFORMATEX *)&wfx, 
											  NULL);
				//m_nBufferSize=bufferDuration*REFERENCE_TIME(m_wfx.Format.nAvgBytesPerSec)/10000000;
			}
			if (FAILED(hr))
			{
				return false;
			}
		}
	}

	_CAudioClient=IAC[0];
	hr = _CAudioClient->SetEventHandle(_CAudioSamplesReadyEvent);
	hr = _CAudioClient->GetService(IID_PPV_ARGS(&_CaptureClient));
	hr = _CAudioClient->GetBufferSize(&_BufferSize);
	switch(m_eDrvType)
	{
		case DIRECT_SOUND:
			break;
		case CORE_AUDIO:
			_LAudioClient=IAC[1];
			hr = _LAudioClient->GetBufferSize(&_BufferSize);
			m_nBufferSize=_BufferSize*_FrameSize;
			hr = _LAudioClient->SetEventHandle(_LAudioSamplesReadyEvent);
			hr = _LAudioClient->GetService(IID_PPV_ARGS(&_RenderClient));
			break;
	}
    return true;
}

IMMDevice* GetDeviceInterface(char* devName,EDataFlow eRole)
{
	HRESULT hr;
    IMMDeviceEnumerator *deviceEnumerator = NULL;
    IMMDeviceCollection *deviceCollection = NULL;
	IMMDevice *device = NULL;
	int nDev=-1;
	hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
	if (SUCCEEDED(hr))
	{
		hr = deviceEnumerator->EnumAudioEndpoints(eRole, DEVICE_STATE_ACTIVE, &deviceCollection);
	}	
	UINT deviceCount;
	if (SUCCEEDED(hr))
	{
		hr = deviceCollection->GetCount(&deviceCount);
	}
	if (SUCCEEDED(hr))
	{
		for(UINT i=0;i<deviceCount;++i)
		{
			LPWSTR deviceId=NULL;
			hr = deviceCollection->Item(i,&device);
			hr = device->GetId(&deviceId);

			IPropertyStore *propertyStore;
			hr = device->OpenPropertyStore(STGM_READ, &propertyStore);
			SafeRelease(&device);

			PROPVARIANT friendlyName;
			PropVariantInit(&friendlyName);
			hr = propertyStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
			SafeRelease(&propertyStore);

			WCHAR tws[128]={0};
			char ts[128]={0};
			hr = StringCbPrintfW(tws, 128, L"%s", STRSAFE_LPCSTR((friendlyName.vt != VT_LPWSTR&&friendlyName.vt != VT_LPSTR) ? L"Unknown" : friendlyName.pwszVal));
			WideCharToMultiByte (CP_OEMCP,NULL,tws,-1,ts, 128,NULL,FALSE);
			PropVariantClear(&friendlyName);
			CoTaskMemFree(deviceId);
			if(lstrcmpA(ts,devName)==0){
				nDev=i;
				break;
			}
		}
		if(nDev>=0)
		{
			hr = deviceCollection->Item(nDev,&device);
		}
	}
	SafeRelease(&deviceCollection);
	SafeRelease(&deviceEnumerator);

	if (SUCCEEDED(hr)&&nDev>=0)
		return device;
	return NULL;
}

bool CAudioHostForCA::SetFmt()
{
	GetDevices();
	if(m_nLDevs<m_iCurLDev||m_nCDevs<m_iCurCDev)
		return false;
	_CEndpoint=GetDeviceInterface(m_pszCDevNames[m_iCurCDev],eCapture);
	if(!_CEndpoint)
	{
		SafeRelease(&_CEndpoint);
		return false;
	}
	
	HRESULT hr = _CEndpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&_CAudioClient));

	switch(m_eDrvType)
	{
		case DIRECT_SOUND:
			break;
		case CORE_AUDIO:
			_LEndpoint=GetDeviceInterface(m_pszLDevNames[m_iCurLDev],eRender);
			if(!_LEndpoint)
			{
				SafeRelease(&_LEndpoint);
				return false;
			}
			hr = _LEndpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&_LAudioClient));
			break;
	}

	if(m_wfx.Format.nAvgBytesPerSec)
	{
		_MixFormat=(WAVEFORMATEX *)(&m_wfx);
		if(m_iInChannels==1)
		{
			_MixFormat->nChannels=1;
			_MixFormat->nBlockAlign = (_MixFormat->wBitsPerSample / 8) * _MixFormat->nChannels;
            _MixFormat->nAvgBytesPerSec = _MixFormat->nSamplesPerSec*_MixFormat->nBlockAlign;
			((WAVEFORMATEXTENSIBLE*)_MixFormat)->dwChannelMask=SPEAKER_FRONT_CENTER;
		}
	}
	else
	{
		hr = _CAudioClient->GetMixFormat(&_MixFormat);

		if(_MixFormat->nChannels==1)
		{
			m_iInChannels=1;
		}
		else
		{
			m_iInChannels=2;
		}
	}
	//assert(_MixFormat != NULL);
	WAVEFORMATEXTENSIBLE twfx={0};
	WAVEFORMATEX* ptwfx=(WAVEFORMATEX*)&twfx;
	WAVEFORMATEXTENSIBLE* ptwfxex=NULL;
    hr = _CAudioClient->IsFormatSupported(/*AUDCLNT_SHAREMODE_SHARED*/AUDCLNT_SHAREMODE_EXCLUSIVE,_MixFormat, /*&ptwfx*/NULL);
    if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT)
    {
        if (_MixFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT/*||
			(_MixFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && 
            reinterpret_cast<WAVEFORMATEXTENSIBLE *>(_MixFormat)->SubFormat == KSDATAFORMAT_SUBTYPE_PCM)*/
			)
        {
            _MixFormat->wFormatTag = WAVE_FORMAT_PCM;
            _MixFormat->wBitsPerSample = 16;
            _MixFormat->nBlockAlign = (_MixFormat->wBitsPerSample / 8) * _MixFormat->nChannels;
            _MixFormat->nAvgBytesPerSec = _MixFormat->nSamplesPerSec*_MixFormat->nBlockAlign;
        }
        else if (_MixFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && 
            reinterpret_cast<WAVEFORMATEXTENSIBLE *>(_MixFormat)->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
        {
            WAVEFORMATEXTENSIBLE *waveFormatExtensible = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(_MixFormat);
            waveFormatExtensible->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
            waveFormatExtensible->Format.wBitsPerSample = 16;
            waveFormatExtensible->Format.nBlockAlign = (_MixFormat->wBitsPerSample / 8) * _MixFormat->nChannels;
            waveFormatExtensible->Format.nAvgBytesPerSec = waveFormatExtensible->Format.nSamplesPerSec*waveFormatExtensible->Format.nBlockAlign;
            waveFormatExtensible->Samples.wValidBitsPerSample = 16;
        }
        else
        {
            return false;
        }
		ptwfx=(WAVEFORMATEX*)&twfx;
        hr = _CAudioClient->IsFormatSupported(/*AUDCLNT_SHAREMODE_SHARED*/AUDCLNT_SHAREMODE_EXCLUSIVE,_MixFormat,/*&ptwfx*/NULL);
        if (FAILED(hr))
        {
            return false;
        }
    }
	if(FAILED(hr))
	{
		return false;
	}
	
	switch(m_eDrvType)
	{
		case DIRECT_SOUND:
			if(m_iInChannels==1)
			{
				_MixFormat->nChannels=2;
				_MixFormat->nBlockAlign=(_MixFormat->wBitsPerSample/8)*_MixFormat->nChannels;
				_MixFormat->nAvgBytesPerSec=_MixFormat->nSamplesPerSec*_MixFormat->nBlockAlign;
				((WAVEFORMATEXTENSIBLE*)_MixFormat)->dwChannelMask=SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT;
			}
			if(!m_pDSO->SetFormat((WAVEFORMATEXTENSIBLE*)_MixFormat))
			{
				return false;
			}
			break;
		case CORE_AUDIO:
			if(m_iInChannels==2)
			{
				ptwfx=(WAVEFORMATEX*)&twfx;
				hr = _LAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE,_MixFormat,NULL);
				//ptwfxex=(WAVEFORMATEXTENSIBLE*)ptwfx;
				if (FAILED(hr))
				{
					return false;
				}
			}
			else if(m_iInChannels==1)
			{
				_MixFormat->nChannels=2;
				_MixFormat->nBlockAlign=(_MixFormat->wBitsPerSample/8)*_MixFormat->nChannels;
				_MixFormat->nAvgBytesPerSec=_MixFormat->nSamplesPerSec*_MixFormat->nBlockAlign;
				((WAVEFORMATEXTENSIBLE*)_MixFormat)->dwChannelMask=SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT;
				hr = _LAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE,_MixFormat,NULL);
				//ptwfxex=(WAVEFORMATEXTENSIBLE*)ptwfx;
				if (FAILED(hr))
				{
					return false;
				}
			}
			else
			{
				return false;
			}
			break;
	}
	
	if(_MixFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
	{
		memcpy(&m_wfx,_MixFormat,sizeof(WAVEFORMATEXTENSIBLE));
	}
	else
	{
		memset(&m_wfx,0,sizeof(WAVEFORMATEXTENSIBLE));
		memcpy(&m_wfx,_MixFormat,sizeof(WAVEFORMATEX));
	}
	return true;
}

bool CAudioHostForCA::Initialize()
{
	if(!SetFmt())
		return false;
	HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_DeviceEnumerator));

    _FrameSize = _MixFormat->nBlockAlign;

	_EnableStreamSwitch=(m_iCurCDev==m_iDefCDev||m_iCurLDev==m_iDefLDev)?true:false;

	_ShutdownEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
    _CAudioSamplesReadyEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
	switch(m_eDrvType)
	{
		case DIRECT_SOUND:
			break;
		case CORE_AUDIO:
			_LAudioSamplesReadyEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
			break;
	}
	
    _StreamSwitchEvent = CreateEvent(NULL,FALSE,FALSE,NULL);

    if (!InitializeAudioEngine())
    {
        return false;
    }
    if (_EnableStreamSwitch)
    {
        if (!InitializeStreamSwitch())
        {
            return false;
        }
    }
    return true;
}

void CAudioHostForCA::Uninitialize()
{
	if (_ShutdownEvent)
    {
        CloseHandle(_ShutdownEvent);
        _ShutdownEvent = NULL;
    }
    if (_CAudioSamplesReadyEvent)
    {
        CloseHandle(_CAudioSamplesReadyEvent);
        _CAudioSamplesReadyEvent = NULL;
    }
	if (_LAudioSamplesReadyEvent)
    {
        CloseHandle(_LAudioSamplesReadyEvent);
        _LAudioSamplesReadyEvent = NULL;
    }
    if (_StreamSwitchEvent)
    {
        CloseHandle(_StreamSwitchEvent);
        _StreamSwitchEvent = NULL;
    }


    SafeRelease(&_CEndpoint);
    SafeRelease(&_CAudioClient);
    SafeRelease(&_CaptureClient);
	SafeRelease(&_LEndpoint);
    SafeRelease(&_LAudioClient);
    SafeRelease(&_RenderClient);
	SafeRelease(&_Volume);

    if (_MixFormat)
    {
        //CoTaskMemFree(_MixFormat);
        _MixFormat = NULL;
    }

    if (_EnableStreamSwitch)
    {
        TerminateStreamSwitch();
    }
	SafeRelease(&_DeviceEnumerator);
}

void CAudioHostForCA::TerminateStreamSwitch()
{
    HRESULT hr = _CAudioSessionControl->UnregisterAudioSessionNotification(this);
	switch(m_eDrvType)
	{
		case DIRECT_SOUND:
			break;
		case CORE_AUDIO:
			hr = _LAudioSessionControl->UnregisterAudioSessionNotification(this);
			break;
	}
    _DeviceEnumerator->UnregisterEndpointNotificationCallback(this);
    if (_StreamSwitchCompleteEvent)
    {
        CloseHandle(_StreamSwitchCompleteEvent);
        _StreamSwitchCompleteEvent = NULL;
    }
    SafeRelease(&_CAudioSessionControl);
	SafeRelease(&_LAudioSessionControl);
}

DWORD CAudioHostForCA::WASAPICaptureThread(LPVOID Context)
{
    CAudioHostForCA *capturer = static_cast<CAudioHostForCA *>(Context);
    return capturer->DoCaptureThread();
}

DWORD CAudioHostForCA::DoCaptureThread()
{
#ifdef DEBUG
				DWORD iPackets=0;
				DWORD dwPacketSize=0;
#endif
	if(CAudioHost::pMixer)
	{
		CAudioHost::SetSampleRate(m_wfx.Format.nSamplesPerSec);
		CAudioHost::SetBufferSize(m_nBufferSize/m_wfx.Format.nBlockAlign);
	}

	CWavVstHost** pVSTs=NULL;
	int nVSTs=0;

	EnterCriticalSection (&m_cs);
	if(m_fVstChanged)
	{
		pVSTs=m_pVSTArray;
		nVSTs=m_nVST;
		if(pVSTs){
			for(int i=0;i<nVSTs;++i)
			{
				pVSTs[i]->SetBuffer(m_wfx.Format.nSamplesPerSec,m_nBufferSize/m_wfx.Format.nBlockAlign);
			}
		}
		m_fVstChanged=FALSE;
	}
	LeaveCriticalSection (&m_cs);

	CoInitializeEx(NULL, COINIT_MULTITHREADED);
    bool stillPlaying = true;
    HANDLE waitArray[3] = {_ShutdownEvent, _StreamSwitchEvent, _CAudioSamplesReadyEvent };
    HANDLE mmcssHandle = NULL;
    DWORD mmcssTaskIndex = 0;
	DWORD dwTimeOut = m_nBufferSize*1000/m_wfx.Format.nAvgBytesPerSec;

	size_t NBS=m_wfx.Format.wBitsPerSample/8;//N bytes Per Sample

    HRESULT hr = S_OK;
    
    mmcssHandle = AvSetMmThreadCharacteristicsA("Audio", &mmcssTaskIndex);

    while (stillPlaying)
    {
        HRESULT hr;
        DWORD waitResult = WaitForMultipleObjects(3, waitArray, FALSE, INFINITE);
        switch (waitResult)
        {
        case WAIT_OBJECT_0 + 0:     // _ShutdownEvent
            stillPlaying = false;       // We're done, exit the loop.
			SetEvent(_ShutdownEvent);
            break;
        case WAIT_OBJECT_0 + 1:     // _StreamSwitchEvent
            //
            //  We need to stop the capturer, tear down the _CAudioClient and _CaptureClient objects and re-create them on the new.
            //  endpoint if possible.  If this fails, abort the thread.
            //
			//Turn Off Auto-Redirection of streams since there is BUGs on APIs.
            /*if (!HandleStreamSwitchEvent())
            {
                stillPlaying = false;
				SetEvent(_ShutdownEvent);
            }*/
            break;
        case WAIT_OBJECT_0 + 2:     // _CAudioSamplesReadyEvent
			BYTE *pData;
			UINT32 framesAvailable;
			DWORD  flags;
			size_t ByteToCopy;
			DWORD  iDsBuffer;

			hr = _CaptureClient->GetBuffer(&pData, &framesAvailable, &flags, NULL, NULL);
			
//#ifdef DEBUG
//				{
//					iPackets++;
//					dwPacketSize=framesAvailable;
//					char t[127]={0};
//					sprintf(t,"Get Packet %i Size %i Frames\n",iPackets,framesAvailable);
//					OutputDebugString(t);
//				}
//#endif
			if (SUCCEEDED(hr))
			{
				ByteToCopy = framesAvailable*_FrameSize;
				if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
				{
					if(m_iInChannels==1)
					{
						memset(pData,0,ByteToCopy/2);
					}
					else
					{
						memset(pData,0,ByteToCopy);
					}
				}
				if(m_iInChannels==1){//Copy one channel Data to two channels
					BYTE* pb=_CaptureBuffer;
					BYTE* ps=(BYTE*)pData;
					int n=framesAvailable;
					BYTE tb=0;
					for(int i=0;i<n;++i)//copy from offset p1 to p2
					{
						DWORD p1=i*NBS,p2=i*2*NBS;
						for(int j=0;j<NBS;++j)
						{
							tb=ps[p1];
							pb[p2]=tb;
							pb[p2+NBS]=tb;
							++p1;
							++p2;
						}
					}
#ifdef DEBUG
			f.write((char*)pData,m_nBufferSize/2);
#endif
				}
				else
				{
					memcpy(_CaptureBuffer,pData,ByteToCopy);
#ifdef DEBUG
			f.write((char*)pData,m_nBufferSize);
#endif
				}
				hr = _CaptureClient->ReleaseBuffer(framesAvailable);
			}

			EnterCriticalSection (&m_cs);
			if(m_fVstChanged)
			{
				pVSTs=m_pVSTArray;
				nVSTs=m_nVST;
				if(pVSTs){
					for(int i=0;i<nVSTs;++i)
					{
						pVSTs[i]->SetBuffer(m_wfx.Format.nSamplesPerSec,m_nBufferSize/m_wfx.Format.nBlockAlign);
					}
				}
				m_fVstChanged=FALSE;
			}
			if(pVSTs)
			{
				for(int i=0;i<nVSTs;++i)
				{
					pVSTs[i]->Process(_CaptureBuffer);
				}
			}
			LeaveCriticalSection (&m_cs);
			////////////////////////////Add Process
			switch(m_wfx.Format.nBlockAlign)
			{
				case 2:
					CAudioHost::HandleSamples8Bit((char*)_CaptureBuffer,m_nBufferSize);
					break;
				case 4:
					CAudioHost::HandleSamples16Bit((char*)_CaptureBuffer,m_nBufferSize);
					break;
				case 6:
					break;
				case 8:
					CAudioHost::HandleSamples32Bit((char*)_CaptureBuffer,m_nBufferSize);
					break;
			}
			////////////////////////////
			switch(m_eDrvType)
			{
				case CORE_AUDIO:
					if(WAIT_TIMEOUT!=WaitForSingleObject(_LAudioSamplesReadyEvent,INFINITE/*dwTimeOut*/))
					{
						hr = _RenderClient->GetBuffer(framesAvailable,&pData);
						if (SUCCEEDED(hr))
						{
							memcpy(pData,_CaptureBuffer,ByteToCopy);
							hr = _RenderClient->ReleaseBuffer(framesAvailable,0);
						}
					}
					else
					{
#ifdef DEBUG
				{
					++iPackets;
					char t[127]={0};
					sprintf(t,"Lost Packets %i\n",iPackets);
					OutputDebugString(t);
				}
#endif
					}
					break;
				case DIRECT_SOUND:
					if((iDsBuffer=WaitForMultipleObjects(2,m_pDSO->m_hMainBufferEvent,FALSE,dwTimeOut))!=WAIT_TIMEOUT)
					{
						iDsBuffer-=WAIT_OBJECT_0;
						m_pDSO->FillMainBuffer(_CaptureBuffer,(int)iDsBuffer);
					}
					break;
			}		
						





            break;
        }
    }
    AvRevertMmThreadCharacteristics(mmcssHandle);
	CoUninitialize();
    return 0;
}

HRESULT CAudioHostForCA::OnDefaultDeviceChanged(EDataFlow Flow, ERole Role, LPCWSTR /*NewDefaultDeviceId*/)
{
    if ((Flow == eCapture||Flow == eRender) && Role == eMultimedia)
    {
		if ((m_iCurCDev==m_iDefCDev&&Flow == eCapture)||(m_iCurLDev==m_iDefLDev&&Flow == eRender))
		{
			if (!_InStreamSwitch)
			{
				_InStreamSwitch = true;
				SetEvent(_StreamSwitchEvent);
			}
			SetEvent(_StreamSwitchCompleteEvent);
		}
    }
    return S_OK;
}

bool CAudioHostForCA::HandleStreamSwitchEvent()
{
    HRESULT hr;
    //assert(_InStreamSwitch);
    hr = _CAudioClient->Stop();
	hr = _LAudioClient->Stop();
    if (FAILED(hr))
    {
        goto ErrorExit;
    }

    hr = _CAudioSessionControl->UnregisterAudioSessionNotification(this);
    if (FAILED(hr))
    {
        goto ErrorExit;
    }
	hr = _LAudioSessionControl->UnregisterAudioSessionNotification(this);
    if (FAILED(hr))
    {
        goto ErrorExit;
    }

    SafeRelease(&_CAudioSessionControl);
    SafeRelease(&_CaptureClient);
    SafeRelease(&_CAudioClient);
    SafeRelease(&_CEndpoint);

	SafeRelease(&_LAudioSessionControl);
    SafeRelease(&_RenderClient);
    SafeRelease(&_LAudioClient);
    SafeRelease(&_LEndpoint);

	SafeRelease(&_Volume);

    DWORD waitResult = WaitForSingleObject(_StreamSwitchCompleteEvent, INFINITE);
    if (waitResult == WAIT_TIMEOUT)
    {
        goto ErrorExit;
    }
	GetDevices();

    hr = _DeviceEnumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, &_CEndpoint);
    if (FAILED(hr))
    {
        goto ErrorExit;
    }
	hr = _DeviceEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &_LEndpoint);
    if (FAILED(hr))
    {
        goto ErrorExit;
    }

    hr = _CEndpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&_CAudioClient));
    if (FAILED(hr))
    {
        goto ErrorExit;
    }
	hr = _LEndpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&_LAudioClient));
    if (FAILED(hr))
    {
        goto ErrorExit;
    }

    /*WAVEFORMATEX *wfxNew;
    hr = _CAudioClient->GetMixFormat(&wfxNew);
    if (FAILED(hr))
    {
        goto ErrorExit;
    }*/

    /*if (memcmp(_MixFormat, wfxNew, sizeof(WAVEFORMATEX) + wfxNew->cbSize) != 0)
    {
        CoTaskMemFree(wfxNew);
        goto ErrorExit;
    }
    CoTaskMemFree(wfxNew);*/


    if (!InitializeAudioEngine())
    {
        goto ErrorExit;
    }

    hr = _CAudioClient->GetService(IID_PPV_ARGS(&_CAudioSessionControl));
    if (FAILED(hr))
    {
        goto ErrorExit;
    }
	hr = _LAudioClient->GetService(IID_PPV_ARGS(&_LAudioSessionControl));
    if (FAILED(hr))
    {
        goto ErrorExit;
    }
    hr = _CAudioSessionControl->RegisterAudioSessionNotification(this);
    if (FAILED(hr))
    {
        goto ErrorExit;
    }
	hr = _LAudioSessionControl->RegisterAudioSessionNotification(this);
    if (FAILED(hr))
    {
        goto ErrorExit;
    }

    ResetEvent(_StreamSwitchCompleteEvent);
   
    hr = _CAudioClient->Start();
    if (FAILED(hr))
    {
        goto ErrorExit;
    }
	hr = _LAudioClient->Start();
    if (FAILED(hr))
    {
        goto ErrorExit;
    }

    _InStreamSwitch = false;
    return true;

ErrorExit:
    _InStreamSwitch = false;
    return false;
}

CAudioHostForCA::CAudioHostForCA(void)
	: m_fIsVista (TRUE)
	, m_nLDevs(0)
	, m_nCDevs(0)
	, m_fIsRun(FALSE)
	, m_iCurLDev(0)
	, m_iCurCDev(0)
	, m_pVSTArray(NULL)
	, m_hThread(NULL)
	, m_nBufferSize(448*4)
	, m_iInChannels(2)
	, m_nVolume(100)
	, m_nVST(0)
	, _RefCount(1)
	, _CEndpoint(NULL)
	, _LEndpoint(NULL)
	, _CAudioClient(NULL)
	, _LAudioClient(NULL)
	, _CaptureClient(NULL)
	, _RenderClient(NULL)
	, _Volume(NULL)
	, _ShutdownEvent(NULL)
	, _CAudioSamplesReadyEvent(NULL)
	, _LAudioSamplesReadyEvent(NULL)
	, _MixFormat(NULL)
	, _FrameSize(16)
	, _BufferSize(0)
	, _CurrentCaptureIndex(0)
	, _CaptureBufferSize(0)
	, _CaptureBuffer(NULL)
	, _CaptureThread(NULL)
	, MAX_BUFFER_SIZE(128000)
	, MIN_BUFFER_SIZE(128)
	, _fCannotUse(FALSE)
	, m_eDrvType(CORE_AUDIO)
	, m_pDSO(NULL)
{
	memset(&m_wfx,0,sizeof(WAVEFORMATEXTENSIBLE));
	CoInitializeEx(NULL, COINIT_MULTITHREADED);
	InitModels();
	/**
	 *	Pickup Devices
	 */
	GetDevices();
	m_iCurLDev=m_iDefLDev;
	m_iCurCDev=m_iDefCDev;
	/**
	 *
	 */
	InitializeCriticalSection (&m_cs) ;
	if(!Initialize())
	{
		_fCannotUse=TRUE;
		return;
	}
	Uninitialize();
}

CAudioHostForCA::~CAudioHostForCA(void)
{
	if(m_pVSTArray)
	{
		free(m_pVSTArray);
		m_pVSTArray=NULL;
		m_nVST=0;
	}
	CoUninitialize();
	DeleteCriticalSection (&m_cs) ;
}

int CAudioHostForCA::GetListenDeviceCount(void)
{
	return m_nLDevs;
}

int CAudioHostForCA::GetCaptureDeviceCount(void)
{
	return m_nCDevs;
}

char* CAudioHostForCA::GetListenDeviceName(int i)
{
	if(i<0||i>=m_nLDevs||m_nLDevs==0)
		return NULL;
	return (char*)m_pszLDevNames[i];
}

char* CAudioHostForCA::GetCaptureDeviceName(int i)
{
	if(i<0||i>=m_nCDevs||m_nCDevs==0)
		return NULL;
	return (char*)m_pszCDevNames[i];
}

int CAudioHostForCA::GetCurrentListenDevice(void)
{
	return m_iCurLDev;
}

BOOL CAudioHostForCA::SetCurrentListenDevice(int i)
{
	if(m_fIsRun||i>m_nLDevs||i<0)
		return FALSE;
	m_iCurLDev=i;
	switch(m_eDrvType)
	{
		case DIRECT_SOUND:
			if(!m_pDSO->SetDevice(i))
			{
				return FALSE;
			}
			break;
		case CORE_AUDIO:
			break;
	}
	//Get minimum buffer size
	if(!Initialize())
		return FALSE;
	Uninitialize();
	return TRUE;
}

BOOL CAudioHostForCA::SetCurrentListenDeviceByName(char* szDeviceName)
{
	for(int i=0;i<m_nLDevs;++i){
		if(stricmp(szDeviceName,m_pszLDevNames[i])==0){
			return SetCurrentListenDevice(i);
		}
	}
	return FALSE;
}

int CAudioHostForCA::GetCurrentCaptureDevice(void)
{
	return m_iCurCDev;
}

BOOL CAudioHostForCA::SetCurrentCaptureDevice(int i)
{
	if(m_fIsRun||i>m_nCDevs||i<0)
		return FALSE;
	m_iCurCDev=i;
	memset(&m_wfx,0,sizeof(m_wfx));
	//Get minimum buffer size
	if(!Initialize())
		return FALSE;
	Uninitialize();
	return TRUE;
}

BOOL CAudioHostForCA::SetCurrentCaptureDeviceByName(char* szDeviceName)
{
	for(int i=0;i<m_nCDevs;++i){
		if(stricmp(szDeviceName,m_pszCDevNames[i])==0){
			return SetCurrentCaptureDevice(i);
		}
	}
	return FALSE;
}

BOOL CAudioHostForCA::Start(void)
{
	if(_fCannotUse)
		return FALSE;
	if(m_fIsRun)
		return FALSE;
	if(!Initialize())
		return FALSE;
	HRESULT hr;

    _CaptureBuffer = new BYTE[m_nBufferSize];
    _CaptureBufferSize = m_nBufferSize;
    _CaptureThread = CreateThread(NULL, 0, WASAPICaptureThread, this, 0, NULL);
	_CEndpoint->AddRef();
	switch(m_eDrvType)
	{
		case DIRECT_SOUND:
			if(!m_pDSO->Start())
			{
				return FALSE;
			}
			break;
		case CORE_AUDIO:
			_LEndpoint->AddRef();
			hr = _LAudioClient->Start();
			if (FAILED(hr))
			{
				return FALSE;
			}
			break;
	}
    hr = _CAudioClient->Start();
    if (FAILED(hr))
    {
        return FALSE;
    }
	m_fIsRun=TRUE;
	return TRUE;
}

void CAudioHostForCA::Stop(void)
{
	if(!m_fIsRun)
		return;
	if (_CaptureThread)
    {
        SetEvent(_ShutdownEvent);
        WaitForSingleObject(_CaptureThread, INFINITE);
        CloseHandle(_CaptureThread);
        _CaptureThread = NULL;
    }
	if(m_eDrvType==DIRECT_SOUND)
	{
		m_pDSO->Stop();
	}
	if(_CaptureBuffer)
	{
		delete[] _CaptureBuffer;
		_CaptureBuffer=NULL;
	}

    Uninitialize();

	m_fIsRun=FALSE;
}

BOOL CAudioHostForCA::SetBufferSize(int size)
{
	if(m_fIsRun)
		return FALSE;
	if(size>MAX_BUFFER_SIZE||size<MIN_BUFFER_SIZE)
		return FALSE;
	m_nBufferSize=(size/128)*128+128;
	
	if(!Initialize())
	{
		Uninitialize();
		return FALSE;
	}
	Uninitialize();
	if(m_eDrvType==DIRECT_SOUND)
	{
		if(!m_pDSO->SetBufferSize(m_nBufferSize))
			return FALSE;
	}
	return TRUE;
}

int CAudioHostForCA::GetBufferSize(void)
{
	return m_nBufferSize;
}

int CAudioHostForCA::SetVolume(int Vol)
{
	if(m_fIsRun){
		float fv=((float)Vol)/100.0f;
		HRESULT hr=S_OK;
		if(fv==0.0f)
		{
			hr=_Volume->SetMute(TRUE,NULL);
		}
		else{
			hr=_Volume->SetMute(FALSE,NULL);
			hr=_Volume->SetMasterVolumeLevelScalar(fv,NULL);
		}
		m_nVolume=(int)(fv*100.0f);
	}
	else
	{
		m_nVolume=Vol;
	}
	return m_nVolume;
}

int CAudioHostForCA::GetVolume(void)
{
	if(m_fIsRun){
		float fv=0.0f;
		HRESULT hr=_Volume->GetMasterVolumeLevelScalar(&fv);
		m_nVolume=(int)(fv*100.0f);
	}
	return m_nVolume;
}

HRESULT CAudioHostForCA::OnSimpleVolumeChanged(float NewSimpleVolume, BOOL NewMute, LPCGUID /*EventContext*/)
{
	/*if(NewMute)
	{
		m_nVolume=0;
	}
	else
	{
		m_nVolume=(int)(NewSimpleVolume*100.0f);
	}*/
	return S_OK;
}

BOOL CAudioHostForCA::SetFormat(WAVEFORMATEX* pwfx)
{
	if(m_fIsRun)
		return FALSE;
	BOOL rt=TRUE;
	if(pwfx)
	{
		memcpy(&m_wfx,pwfx,sizeof(WAVEFORMATEX));
	}
	if(!SetFmt())
	{
		memset(&m_wfx,0,sizeof(WAVEFORMATEX));
		rt=FALSE;
	}
	SafeRelease(&_CAudioClient);
	SafeRelease(&_CEndpoint);
	SafeRelease(&_LAudioClient);
	SafeRelease(&_LEndpoint);

	return rt;
}

void CAudioHostForCA::GetFormat(WAVEFORMATEX* pwfx)
{
	if(pwfx)
	{
		if(m_wfx.Format.nAvgBytesPerSec==0)
		{
			SetFmt();
			SafeRelease(&_CAudioClient);
			SafeRelease(&_CEndpoint);
			SafeRelease(&_LAudioClient);
			SafeRelease(&_LEndpoint);
		}
		memcpy(pwfx,&m_wfx,sizeof(WAVEFORMATEX));
	}
}

ULONG CAudioHostForCA::AddRef()
{
    return InterlockedIncrement(&_RefCount);
}

ULONG CAudioHostForCA::Release()
{
    ULONG returnValue = InterlockedDecrement(&_RefCount);
    if (returnValue == 0)
    {
        delete this;
    }
    return returnValue;
}

HRESULT CAudioHostForCA::QueryInterface(REFIID Iid, void **Object)
{
    if (Object == NULL)
    {
        return E_POINTER;
    }
    *Object = NULL;

    if (Iid == IID_IUnknown)
    {
        *Object = static_cast<IUnknown *>(static_cast<IAudioSessionEvents *>(this));
        AddRef();
    }
    else if (Iid == __uuidof(IMMNotificationClient))
    {
        *Object = static_cast<IMMNotificationClient *>(this);
        AddRef();
    }
    else if (Iid == __uuidof(IAudioSessionEvents))
    {
        *Object = static_cast<IAudioSessionEvents *>(this);
        AddRef();
    }
    else
    {
        return E_NOINTERFACE;
    }
    return S_OK;
}

HRESULT CAudioHostForCA::OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason)
{
    if (DisconnectReason == DisconnectReasonDeviceRemoval)
    {
        _InStreamSwitch = true;
        SetEvent(_StreamSwitchEvent);
    }
    /*if (DisconnectReason == DisconnectReasonFormatChanged)
    {
        _InStreamSwitch = true;
        SetEvent(_StreamSwitchEvent);
        SetEvent(_StreamSwitchCompleteEvent);
    }*/
    return S_OK;
}

BOOL CAudioHostForCA::SetVstEffect(CWavVstHost** pVSTArray,int nCount)
{
	if(nCount<0)
	{
		return FALSE;
	}
	EnterCriticalSection (&m_cs);
	if(m_pVSTArray)
	{
		free(m_pVSTArray);
		m_pVSTArray=NULL;
		m_nVST=0;
	}
	if(nCount>0&&pVSTArray!=NULL)
	{
		m_pVSTArray=(CWavVstHost**)malloc(sizeof(CWavVstHost*)*nCount);
		m_nVST=nCount;
		for(int i=0;i<nCount;++i)
		{
			m_pVSTArray[i]=pVSTArray[i];
		}
	}
	m_fVstChanged=TRUE;
	LeaveCriticalSection (&m_cs);
	return TRUE;
}

BOOL CAudioHostForCA::UseDirectSoundOutput(BOOL fUseDirectSoundOutput)
{
	if(fUseDirectSoundOutput)
	{
		m_pDSO=new CDsoundOutput;
		if(!m_pDSO->m_fInited)
		{
			delete m_pDSO;
			m_pDSO=NULL;
			return FALSE;
		}
		memcpy(m_pszLDevNames,m_pDSO->m_pszLDevNames,128*16);
		m_iCurLDev=m_pDSO->m_iCurLDev;
		m_nLDevs=m_pDSO->m_nLDevs;
		m_eDrvType=DIRECT_SOUND;
		if(!m_pDSO->SetFormat(&m_wfx))
		{
			delete m_pDSO;
			m_pDSO=NULL;
			return FALSE;
		}
		SetBufferSize(m_wfx.Format.nAvgBytesPerSec*40/1000);
	}
	else
	{
		if(m_pDSO)
		{
			delete m_pDSO;
			m_pDSO=NULL;
		}
		m_eDrvType=CORE_AUDIO;
		////////////////
		GetDevices();
		m_iCurLDev=m_iDefLDev;
		////////////////
	}
	memset(&m_wfx,0,sizeof(m_wfx));
	return SetCurrentListenDevice(m_iCurLDev);
}
